#include <stddef.h>
#include <stdlib.h>
#include <assert.h>
#include "arena.h"

#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
#   define PLATFORM_WINDOWS 1
#   define WIN32_LEAN_AND_MEAN
#   include <windows.h>
#else
#   define PLATFORM_POSIX 1
#   include <unistd.h>
#endif /* defined(_WIN32)... */

#define ALIGNMENT sizeof(void*)
#define ALIGN(size, alignto) \
	(((size_t)(size) + (alignto) - 1) & ~((alignto) - 1))

static __inline size_t os_pagesize(void)
{

#if defined(PLATFORM_POSIX)
#define DEFAULT_PAGESIZE 4096
	long int pagesize = sysconf(_SC_PAGESIZE);
	if(pagesize < 0)
		pagesize = DEFAULT_PAGESIZE;
	return (size_t)pagesize;
#undef DEFAULT_PAGESIZE
#else /* PLATFORM_WINDOWS */
	SYSTEM_INFO si;
	GetSystemInfo(&si);
	return (size_t)si.dwPageSize;
#endif
}

struct arena* arena_create(size_t blocksize)
{
	struct arena* a = NULL;
	blocksize = blocksize ? ALIGN(blocksize, ALIGNMENT) : os_pagesize();
	a = (struct arena*)malloc(blocksize);
	if(a == NULL)
		return NULL;
    a->blocks = NULL;
	a->currpos = (char*)&a[1];
	a->end = (char*)a + blocksize;
	a->blocksize = blocksize;
	a->malloced = 1;
	return a;
}

struct arena* arena_create_static(void* buf, size_t bufsize)
{
	struct arena* a = NULL;
	assert(buf);
	assert(bufsize);
	assert(bufsize == ALIGN(bufsize, ALIGNMENT));
	a = (struct arena*)buf;
    a->blocks = NULL;
	a->currpos = (char*)&a[1];
	a->end = (char*)a + bufsize;
	a->blocksize = bufsize;
	a->malloced = 0;
	return a;
}

void* arena_malloc(struct arena* a, size_t size)
{
	void* p = NULL;
	assert(a);
	if(size == 0)
		return NULL;
	size = ALIGN(size, ALIGNMENT);
	if(a->currpos + size > a->end)
	{
		struct memblock* block = NULL;
		size_t n = ALIGN(sizeof(struct memblock) + size, a->blocksize);
		block = (struct memblock*)malloc(n);
		if(block == NULL)
			return NULL;
		block->size = n;
		block->next = a->blocks;
		a->blocks = block;
		a->currpos = (char*)&block[1];
		a->end = (char*)block + n;
	}
	p = a->currpos;
	a->currpos += size;
	return p;
}

void arena_reset(struct arena* a)
{
	struct memblock* p = NULL;
	assert(a);
	while((p = a->blocks))
	{
		a->blocks = a->blocks->next;
		free(p);
	}
	a->currpos = (char*)&a[1];
	a->end = (char*)a + a->blocksize;
}

void arena_destroy(struct arena* a)
{
	struct memblock* p = NULL;
	assert(a);
	while((p = a->blocks))
	{
		a->blocks = a->blocks->next;
		free(p);
	}
	if(a->malloced)
		free(a);
}
